package com.jdd.fintech.morse.plugin.util;

import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Strings;
import com.jdd.fintech.morse.plugin.ecrypts.*;
import com.jdd.fintech.morse.plugin.enums.EncrAndDecrAlgorithmTypeEnum;
import com.jdd.fintech.morse.plugin.plugconfig.MorsePlugValueConfig;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

import javax.crypto.spec.IvParameterSpec;
import java.security.MessageDigest;

import static com.jdd.fintech.morse.plugin.constans.OpenApiConstans.*;


/**
 * @Description: 默认使用
 * @Author: fengcheng
 * @Date: 2021/5/27
 */
public class CryptoUtils {

    private static final Logger logger = LoggerFactory.getLogger(CryptoUtils.class);

    private static final String[] HEX_DIGITS = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};

    private CryptoUtils() {
    }

    /**
     * 获取对称加密的对象
     *
     * @return
     */
    public static SymmetricCrypto generateSymmetricCrypto(EncrAndDecrAlgorithmTypeEnum sa, String secretKey, String encryptMode, Integer secretKeyLength) {
        Assert.notNull(sa, "加密算法参数不能为空");
        Assert.notNull(secretKey, "密钥不能为空");
        SymmetricCrypto sc = null;
        switch (sa) {
            case AES:
                if (StringUtils.isNotBlank(encryptMode)) {
                    sc = new AES(encryptMode, AES_CBC_ENCRYPT_MODE, secretKey, secretKeyLength, new IvParameterSpec(secretKey.getBytes()));
                } else {
                    sc = new AES(secretKey);
                }
                break;
            case DES:
                if (StringUtils.isNotBlank(encryptMode)) {
                    sc = new DES(encryptMode, AES_CBC_ENCRYPT_MODE, secretKey, secretKeyLength, new IvParameterSpec(secretKey.getBytes()));
                } else {
                    sc = new DES(secretKey);
                }
                break;
            case TRIPLE_DES:
                if (StringUtils.isNotBlank(encryptMode)) {
                    sc = new TripleDES(encryptMode, AES_CBC_ENCRYPT_MODE, secretKey, secretKeyLength, new IvParameterSpec(secretKey.getBytes()));
                } else {
                    sc = new TripleDES(secretKey);
                }
                break;
            case SM4:
                if (StringUtils.isNotBlank(encryptMode)) {
                    sc = new SM4(encryptMode, AES_CBC_ENCRYPT_MODE, secretKey, secretKeyLength, new IvParameterSpec(secretKey.getBytes()));
                } else {
                    sc = new SM4(secretKey);
                }
                break;
            default:
                throw new IllegalArgumentException("暂不支持该种加密方式");
        }
        return sc;
    }

    /**
     * 签名
     *
     * @param data
     * @param salt
     * @return
     * @throws Exception
     */
    public static String sign(String data, String salt) throws Exception {
        data = data + "|" + salt;
        byte[] bytes = MessageDigest.getInstance("MD5").digest(data.getBytes("UTF-8"));
        return byteToString(bytes);
    }

    public static String signMS(String data) throws Exception {
        byte[] bytes = MessageDigest.getInstance("MD5").digest(data.getBytes("UTF-8"));
        return byteToString(bytes);
    }

    private static String byteToString(byte[] bByte) {
        StringBuilder sb = new StringBuilder();
        for (byte aBByte : bByte) {
            sb.append(byteToArrayString(aBByte));
        }
        return sb.toString();
    }

    private static String byteToArrayString(byte bByte) {
        int iRet = bByte;
        if (iRet < 0) {
            iRet += 256;
        }
        int iD1 = iRet / 16;
        int iD2 = iRet % 16;
        return HEX_DIGITS[iD1] + HEX_DIGITS[iD2];
    }

    /**
     * 验签
     *
     * @param salt
     * @param requestData
     * @param sysSign
     * @return
     */
    public static boolean verifySign(String salt, String requestData, String sysSign) {
        String checkSign;
        try {
            checkSign = AesUtils.sign(requestData, salt);
            if (!Strings.isNullOrEmpty(checkSign) && checkSign.equals(sysSign)) {
                logger.info("verifySign|Morse密文|验签成功！");
                return true;
            }
            logger.error("verifySign|Morse密文|验签失败！");
            return false;
        } catch (Exception e) {
            logger.error("verifySign|Morse密文|验签异常！|requestData:{} sysSign:{} 异常信息：{}", requestData, sysSign, e.getMessage(), e);
            return false;
        }
    }

    /**
     * 验签
     *
     * @param salt
     * @param requestData
     * @param sysSign
     * @return
     */
    public static boolean verifySignMS(String salt, JSONObject requestData, String sysSign, String dataDecrypted) {

        try {
            String decrypedReqStr = "token=" + requestData.get("token") + "&source=" + requestData.get("source") + "&version=" + requestData.get("version") + "&requestTime=" + requestData.get("requestTime") + "&data="+dataDecrypted;
            logger.info("verifySignMS验签 ： " + decrypedReqStr);
            String signatureReq = Md5Util.md5Lower32(null, decrypedReqStr, salt);
            if (!Strings.isNullOrEmpty(signatureReq) && requestData.get("signature").equals(signatureReq)) {
                logger.info("verifySign|Morse密文|验签成功！");
                return true;
            }
            logger.error("verifySign|Morse密文|验签失败！");
            return false;
        } catch (Exception e) {
            logger.error("verifySign|Morse密文|验签异常！|requestData:{} sysSign:{} 异常信息：{}", requestData, sysSign, e.getMessage(), e);
            return false;
        }
    }
    /**
     * 检查加密相对应的密钥长度是否超长，最大长度是256
     *
     * @param plugValueConfig
     * @return
     */
    public static void checkSecurityLength(String securityKey, MorsePlugValueConfig plugValueConfig) {
        if (StringUtils.isBlank(securityKey) || ObjectUtils.isEmpty(plugValueConfig)) {
            throw new RuntimeException("插件值或者密钥为空，请检查插件值配置，加密失败！");
        }
        int keySize = plugValueConfig.getKeySize();
        EncrAndDecrAlgorithmTypeEnum encyAndDecyType = plugValueConfig.getEncyAndDecyType();
        int securityKeyLength = securityKey.length();
        if (securityKeyLength != keySize || (securityKeyLength > MAX_KEY_SECRET_KEY_LENGTH || keySize > MAX_KEY_SECRET_KEY_LENGTH)) {
            throw new RuntimeException("密钥长度不匹配，请检查插件值配置，加密失败！");
        }
        switch (encyAndDecyType) {
            case AES:
                if (keySize > AES_SECRET_KEY_LENGTH) {
                    throw new RuntimeException("加密方式为：AES，密钥长度过长，加密失败，密钥长度超过" + AES_SECRET_KEY_LENGTH + "位");
                }
                break;
            case DES:
                if (keySize > TRIPLE_DES_SECRET_KEY_LENGTH) {
                    throw new RuntimeException("加密方式为：DES，密钥长度过长，加密失败，密钥长度超过" + TRIPLE_DES_SECRET_KEY_LENGTH + "位");
                }
                break;
            case TRIPLE_DES:
                if (keySize > TRIPLE_DES_SECRET_KEY_LENGTH) {
                    throw new RuntimeException("加密方式为：TRIPLE_DES，密钥长度过长，加密失败，密钥长度超过" + TRIPLE_DES_SECRET_KEY_LENGTH + "位");
                }
                break;
            case SM4:
                if (keySize > SM4_SECRET_KEY_LENGTH) {
                    throw new RuntimeException("加密方式为：SM4，密钥长度过长，加密失败，密钥长度超过" + SM4_SECRET_KEY_LENGTH + "位");
                }
                break;
            case PUBLIC_RSA:
                logger.warn("采用非对称式加解密不进行校验公钥私钥长度！");
                break;
            default:
                throw new IllegalArgumentException("暂不支持该种加密方式");
        }
    }
}
